힙 (자료 구조)
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
힙은 이진 트리 기반의 자료구조로, 각 노드가 특정 키 값을 가지며, 부모 노드의 키 값은 자식 노드의 키 값보다 크거나 작도록 구성된다. 힙은 여러 종류가 있으며, 최대 힙과 최소 힙이 대표적이다. 최대 힙은 부모 노드가 자식 노드보다 크거나 같은 값을 가지며, 최소 힙은 부모 노드가 자식 노드보다 작거나 같은 값을 가진다.
힙은 배열로 구현되며, 삽입, 삭제 연산의 시간 복잡도는 O(log n)이다. 힙은 힙 정렬, 우선순위 큐, 그래프 알고리즘 등 다양한 분야에 활용된다. 여러 프로그래밍 언어에서 힙을 구현한 라이브러리를 제공한다.
더 읽어볼만한 페이지
힙 (자료 구조) | |
---|---|
자료 구조 | |
![]() | |
유형 | 자료 구조 |
클래스 | 우선순위 큐 |
시간 복잡도 (빅 오 표기법) | |
평균 | 삽입: O(1) 삭제: O(log n) 검색: O(n) |
최악 | 삽입: O(log n) 삭제: O(log n) 검색: O(n) |
공간 복잡도 | |
최악 | O(n) |
2. 종류
힙은 다음과 같은 종류가 있다.
- 이진 힙
- 이항 힙
- 피보나치 힙
- 2-3 heap|2-3 힙영어
- Beap|Beap영어
- D-ary heap|D-ary 힙영어
- Leftist heap|좌편향 힙영어 (왼쪽 힙)
- 페어링 힙
- Skew heap|스큐 힙영어 (왜곡 힙)
- Soft heap|소프트 힙영어
- Ternary heap|삼진 힙영어
이 외에도, 최대 힙과 최소 힙과 같이 부모/자식 요소 간의 크기 관계에 제약 조건을 가지는 힙도 있다.
2. 1. 최대 힙 (Max Heap)
최대 힙은 "자식 요소는 부모 요소보다 항상 작거나 같음"이라는 제약을 갖는 트리 구조이다. 자식 요소가 여러 개인 경우, 자식 요소 간의 크기 관계에는 제약이 없다. 힙은 최소값(또는 최대값)을 구하는 데 적합하다.2. 2. 최소 힙 (Min Heap)
최소 힙은 "자식 요소는 부모 요소보다 항상 크거나 같음"이라는 제약을 갖는 트리 구조이다. 자식 요소가 여러 개인 경우, 자식 요소 간의 크기 관계에는 제약이 없다. 최소 힙은 우선순위 큐 구현이나 프림 알고리즘, 다익스트라 알고리즘과 같은 그래프 문제 알고리즘에서 사용된다.2. 3. 기타 변형
- 2–3 힙
- B-힙
- 비프
- 이진 힙
- 이항 힙
- 브로달 큐
- d-ary 힙
- 피보나치 힙
- K-D 힙
- 리프 힙
- 왼쪽 힙 (좌편향 힙)
- 왜곡 이항 힙
- 엄격한 피보나치 힙
- 최소-최대 힙
- 페어링 힙
- 래딕스 힙
- 랜덤 멜드 가능 힙
- 왜곡 힙 (스큐 힙)
- 소프트 힙
- 삼진 힙
- 트리프
- 약한 힙
3. 이진 힙 (Binary Heap)
이진 힙은 이진 트리를 기반으로 하는 자료 구조로, 특정한 속성을 만족하도록 구성된다.
이진 힙은 배열을 이용하여 구현할 수 있다. 배열을 사용하면 힙의 노드와 그 관계를 쉽게 나타낼 수 있다.
- 배열의 각 요소는 힙의 노드를 나타낸다.
- 부모/자식 관계는 배열 요소의 인덱스를 통해 암시적으로 정의된다.
이진 힙의 경우, 배열의 첫 번째 인덱스에는 루트 요소가 저장된다. 다음 두 인덱스에는 루트의 자식들이, 그 다음 네 개의 인덱스에는 루트의 두 자식 노드의 네 자식들이 저장되는 방식이다. 예를 들어, 인덱스 i인 노드의 자식은 2i + 1과 2i + 2에 위치하고, 부모는 (i-1)/2 (소수점 이하 버림)에 위치한다. 이러한 인덱싱 방식은 트리를 효율적으로 탐색하는 데 도움이 된다.
힙의 균형을 맞추기 위해 sift-up 또는 sift-down 연산이 사용된다. 이 연산은 정렬되지 않은 요소를 교환하여 힙의 속성을 유지한다. 배열을 사용하면 추가적인 메모리 없이 힙을 만들 수 있으므로, 힙 정렬을 사용하여 배열을 제자리에서 정렬할 수 있다.
힙에 요소를 삽입하거나 삭제하면 힙 속성이 위반될 수 있다. 이 경우 배열 내에서 요소를 교환하여 힙의 균형을 다시 맞춰야 한다.
일반적인 힙의 연산은 다음과 같다.
- '''삽입''': 힙의 끝, 즉 첫 번째 사용 가능한 빈 공간에 새 요소를 추가한다. 힙 속성을 위반하는 경우, 힙 속성이 다시 설정될 때까지 새 요소를 sift up (''swim'' 연산)한다.
- '''추출''': 루트를 제거하고 힙의 마지막 요소를 루트에 삽입한다. 힙 속성을 위반하는 경우, 힙 속성을 다시 설정하기 위해 새 루트를 sift down (''sink'' 연산)한다.
- '''교체''': 루트를 제거하고 "새" 요소를 루트에 넣고 sift down한다. 삽입 후 추출과 비교하면 sift up 단계를 피할 수 있다.
주어진 요소 배열에서 이진 힙을 구성하는 것은 Floyd 알고리즘을 사용하여 선형 시간에 수행할 수 있다.[7]
3. 1. 구조
이진 힙은 내부 노드에 키와 요소를 저장하는 이진 트리로, 다음과 같은 두 가지 특징을 갖는다.- 뿌리 노드를 제외한 각 내부 노드는 `key(T.parent(v)) < key(v)` 또는 `key(T.parent(v)) > key(v)`이다. (키 값은 오름차순 또는 내림차순)
- 마지막 왼쪽 결합 노드들의 레벨을 제외한 다른 모든 레벨들은 완전 이진 트리를 형성한다.[9]
힙은 힙 리스트로 표현할 수 있는데, i번째 노드의 왼쪽 자식 노드 위치는 2i, 오른쪽 자식 노드 위치는 2i+1, 부모 노드 위치는 i/2가 된다.
예를 들어, 배열
[H|E|A|P|S|O|R|T]
를 사전 순서(A1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 설명 |
---|---|---|---|---|---|---|---|---|
H | E | A | P | S | O | R | T | 중간 지점인 P(4번째 인덱스)에서 시작. T>P이므로 교환. |
H | E | A | T | S | O | R | P | A(3번째 인덱스)의 왼쪽 자식 노드는 O(6번째 인덱스), 오른쪽 자식 노드는 R(7번째 인덱스). R>O, R>A이므로 R, A 교환. |
H | E | R | T | S | O | A | P | E(인덱스 2)와 왼쪽 자식 노드 T(인덱스 4), 오른쪽 자식 노드 S(인덱스 5) 비교. T>S, T>E이므로 T, E 교환. |
H | T | R | E | S | O | A | P | E(인덱스 4)는 왼쪽 자식 노드 P(인덱스 8)과 비교. P>E이므로 교환. |
H | T | R | P | S | O | A | E | H(인덱스 1)은 왼쪽 자식 노드 T(인덱스 2)와 오른쪽 자식 노드 R(인덱스 3) 비교. T>R, T>H이므로 교환. |
T | H | R | P | S | O | A | E | H(인덱스 2)의 왼쪽 자식 노드 P(인덱스 4), 오른쪽 자식 노드 S(인덱스 5) 비교. S>P, S>H이므로 교환. |
T | S | R | P | H | O | A | E | 힙 구성 완료. |
힙 구성 결과, 뿌리 노드 T는 자식 노드 S, R보다 크며(T>S, T>R), 동일한 관계가 모든 노드와 그 자식 노드에 대해 성립한다.
힙은 일반적으로 배열로 구현되며, 각 요소는 힙의 노드를 나타낸다. 부모/자식 관계는 배열의 요소 인덱스를 통해 암시적으로 정의된다.
이진 힙의 경우, 배열의 첫 번째 인덱스는 루트 요소, 다음 두 인덱스는 루트의 자식, 다음 네 인덱스는 루트의 두 자식 노드의 네 자식을 포함하는 식으로 진행된다. 따라서 인덱스 i의 노드가 주어지면, 자식은 인덱스 2i+1과 2i+2에, 부모는 인덱스 ⌊(i-1)/2⌋에 있다.
힙의 균형은 sift-up 또는 sift-down 연산(정렬되지 않은 요소를 교환)으로 수행된다. 추가 메모리 없이 배열에서 힙을 만들 수 있으므로, 힙 정렬을 사용하여 배열을 제자리에서 정렬할 수 있다.
힙에 요소를 삽입하거나 삭제한 후에는 힙 속성이 위반될 수 있으며, 배열 내에서 요소를 교환하여 힙의 균형을 다시 맞춰야 한다.
일반적인 힙 연산 방법은 다음과 같다.
- 삽입: 힙의 끝(첫 번째 사용 가능한 빈 공간)에 새 요소를 추가. 힙 속성이 위반되면, 힙 속성이 다시 설정될 때까지 새 요소를 sift up (swim 연산)한다.
- 추출: 루트를 제거하고 힙의 마지막 요소를 루트에 삽입. 힙 속성이 위반되면, 힙 속성을 다시 설정하기 위해 새 루트를 sift down (sink 연산)한다.
- 교체: 루트를 제거하고 "새" 요소를 루트에 넣고 sift down한다. (sift up 단계 생략 가능)
주어진 요소 배열에서 이진(또는 d-ary) 힙을 구성하는 것은 Floyd 알고리즘을 사용하여 선형 시간에 수행할 수 있다.[7]
부모 요소는 항상 두 개의 자식 요소보다 크거나 작게 구성되어 있다. 삽입, 삭제는 O(log n)으로 가능하며, 탐색은 O(n)이다. 루트가 항상 최소(또는 최대) 요소이므로, 루트 삭제를 반복하여 정렬을 수행할 수 있다. (계산량: O(n log n), 힙 정렬)
낮은 쪽(또는 얕은 쪽)부터 요소를 모은 트리 구조를 만든다. 깊이 n의 요소가 모두 사용될 때까지 깊이 n+1의 요소는 생성하지 않는다. 요소 첨자를 1부터 시작하면, 요소 n의 부모는 n÷2, 자식은 2n 및 2n+1이 된다. (첨자를 0부터 시작하면 부모는 (n-1)÷2, 자식은 2n+1과 2n+2이다).
힙은 데이터 출현 순서에 관계없이 이러한 구조를 쉽게 유지할 수 있다는 장점이 있다.
3. 2. 복잡도
힙의 시간 복잡도는 이다. 이진 트리의 속성 에서, 이므로 힙 트리의 높이가 이 됨을 알 수 있다.3. 3. 구현
힙은 일반적으로 배열로 구현된다.
- 배열의 각 요소는 힙의 노드를 나타낸다.
- 부모/자식 관계는 배열의 요소 인덱스를 통해 암시적으로 정의된다.
이진 힙의 경우, 배열에서 첫 번째 인덱스는 루트 요소를 포함한다. 배열의 다음 두 인덱스는 루트의 자식을, 다음 네 개의 인덱스는 루트의 두 자식 노드의 네 자식을 포함하며, 이런 식으로 진행된다. 따라서 인덱스 i의 노드가 주어지면, 자식은 인덱스 2i + 1과 2i + 2에 있으며, 부모는 인덱스 에 있다. 이 간단한 인덱싱 체계는 트리를 "위" 또는 "아래"로 이동하는 데 효율적이다.
힙의 균형을 맞추는 것은 sift-up 또는 sift-down 연산(정렬되지 않은 요소를 교환)으로 수행된다. 추가 메모리 없이 배열에서 힙을 만들 수 있으므로 힙 정렬을 사용하여 배열을 제자리에서 정렬할 수 있다.
힙에 요소를 삽입하거나 삭제한 후에는 힙 속성이 위반될 수 있으며, 배열 내에서 요소를 교환하여 힙의 균형을 다시 맞춰야 한다.
다양한 유형의 힙이 연산을 다르게 구현하지만, 가장 일반적인 방법은 다음과 같다.
- '''삽입:''' 힙의 끝, 즉 첫 번째 사용 가능한 빈 공간에 새 요소를 추가한다. 힙 속성을 위반하는 경우, 힙 속성이 다시 설정될 때까지 새 요소를 sift up 한다.
- '''추출:''' 루트를 제거하고 힙의 마지막 요소를 루트에 삽입한다. 힙 속성을 위반하는 경우, 힙 속성을 다시 설정하기 위해 새 루트를 sift down 한다.
- '''교체:''' 루트를 제거하고 "새" 요소를 루트에 넣고 sift down한다. 삽입 후 추출과 비교하면 sift up 단계를 피할 수 있다.
주어진 요소 배열에서 이진 힙을 구성하는 것은 Floyd 알고리즘을 사용하여 선형 시간에 수행할 수 있으며, 최악의 경우 비교 횟수는 2''N'' − 2''s''2(''N'') − ''e''2(''N'') (이진 힙의 경우)와 같다.[7] 여기서 ''s''2(''N'')는 ''N''의 이진 표현의 모든 숫자의 합이고, ''e''2(''N'')는 ''N''의 소인수 분해에서 2의 지수이다. 이는 처음에는 비어 있는 힙에 일련의 연속적인 삽입을 수행하는 것보다 빠르다.
다음은 배열을 최대 힙으로 구성하는 과정을 보여주는 예시이다.
`[H|E|A|P|S|O|R|T]`
이 배열을 사전 순서(A < B < C < … < Y < Z)에 따라 힙으로 구성하는 과정은 다음과 같다.
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 설명 |
---|---|---|---|---|---|---|---|---|
H | E | A | P | S | O | R | T | 중간 지점인 p (4번째 인덱스)에서 시작 |
H | E | A | T | S | O | R | P | p의 왼쪽 자식(8번째 인덱스) T > P이므로 교환 |
H | E | R | T | S | O | A | P | A (3번째 인덱스)의 왼쪽 자식은 O (6번째 인덱스), 오른쪽 자식은 R (7번째 인덱스). R > O, R > A이므로 R과 A 교환 |
H | T | R | E | S | O | A | P | E (2번째 인덱스)의 왼쪽 자식은 T (4번째 인덱스), 오른쪽 자식은 S (5번째 인덱스). T > S, T > E이므로 T와 E 교환 |
H | T | R | P | S | O | A | E | E (4번째 인덱스)의 왼쪽 자식은 P (8번째 인덱스). P > E이므로 P와 E 교환 |
T | H | R | P | S | O | A | E | H (1번째 인덱스)의 왼쪽 자식은 T (2번째 인덱스), 오른쪽 자식은 R (3번째 인덱스). T > R, T > H이므로 T와 H 교환 |
T | S | R | P | H | O | A | E | H (2번째 인덱스)의 왼쪽 자식은 P (4번째 인덱스), 오른쪽 자식은 S (5번째 인덱스). S > P, S > H이므로 S와 H 교환. 힙 구성 완료. |
힙 구성 결과, 뿌리 노드인 T는 그 자식 노드인 S, R과 비교할 때 T > S, T > R 관계를 가지며, 동일한 관계가 모든 노드와 그 자식 노드에 대해 성립함을 볼 수 있다.
이진 트리를 힙으로 구성하는 과정의 개략적인 의사 코드는 아래와 같다.
makeTreeHeap(H,n) //H full binary Tree, data size n
for(i <= H/2;i>=1;i <- i-1) do //부모노드 레벨의 역행(4-,3-,2-,1번째)
{
p <- i;// 중간지점에서 시작
for(j<- 2*p;j<=n;j <- 2*j) do //해당 부모노드의 자식노드들에 대한 비교 교환
{
if(j
if(H[j] < H[j+1]) then j <- j+1
if(H[p] >= H[j]) exit;
temp <- H[p];
H[p] <- H[j];
H[j] <- temp;
p <- j;
}
}
임의의 요소에 대한 부모 요소와 자식 요소는 인덱스 계산으로 특정할 수 있다. 또한 요소가 존재하는지 여부는 전체 요소 수와 비교하여 판단할 수 있다. 이 때문에 포인터에 해당하는 데이터를 가지지 않아도, 데이터 자체를 저장한 배열만으로 힙 구조를 구현하는 것이 가능하다.
하지만 개별 요소의 데이터 용량이 큰 경우에는 요소 교체 시 복사 처리의 부하가 문제가 될 수도 있다. 대책으로, 각 요소에 대한 포인터(또는 각 요소의 인덱스)로 구성된 배열을 별도로 생성하여, 이 배열로 힙 구조를 구현하는 선택도 고려할 수 있다.
3. 3. 1. 배열을 이용한 구현
힙은 일반적으로 배열을 이용하여 구현할 수 있다. 배열을 사용하면 힙의 노드와 그 관계를 쉽게 나타낼 수 있다.
- 배열의 각 요소는 힙의 노드를 나타낸다.
- 부모/자식 관계는 배열 요소의 인덱스를 통해 암시적으로 정의된다.
이진 힙의 경우, 배열의 첫 번째 인덱스에는 루트 요소가 저장된다. 다음 두 인덱스에는 루트의 자식들이, 그 다음 네 개의 인덱스에는 루트의 두 자식 노드의 네 자식들이 저장되는 방식이다. 예를 들어, 인덱스 i인 노드의 자식은 2i + 1과 2i + 2에 위치하고, 부모는 (i - 1) / 2 (소수점 이하 버림)에 위치한다. 이러한 인덱싱 방식은 트리를 효율적으로 탐색하는 데 도움이 된다.
힙의 균형을 맞추기 위해 sift-up 또는 sift-down 연산이 사용된다. 이 연산은 정렬되지 않은 요소를 교환하여 힙의 속성을 유지한다. 배열을 사용하면 추가적인 메모리 없이 힙을 만들 수 있으므로, 힙 정렬을 사용하여 배열을 제자리에서 정렬할 수 있다.
힙에 요소를 삽입하거나 삭제하면 힙 속성이 위반될 수 있다. 이 경우 배열 내에서 요소를 교환하여 힙의 균형을 다시 맞춰야 한다.
일반적인 힙 연산의 구현 방법은 다음과 같다.
- '''삽입:''' 힙의 끝(첫 번째 사용 가능한 빈 공간)에 새 요소를 추가한다. 힙 속성이 위반되면, 힙 속성이 복구될 때까지 새 요소를 sift up (''swim'' 연산)한다.
- '''추출:''' 루트를 제거하고 힙의 마지막 요소를 루트에 삽입한다. 힙 속성이 위반되면, 힙 속성이 복구될 때까지 새 루트를 sift down (''sink'' 연산)한다.
- '''교체:''' 루트를 제거하고 "새" 요소를 루트에 넣고 sift down한다. 이렇게 하면 삽입 후 추출 시 sift up 단계를 생략할 수 있다.
주어진 요소 배열에서 이진(또는 d-ary) 힙을 구성하는 것은 Floyd 알고리즘을 사용하여 선형 시간에 수행할 수 있다. 최악의 경우 비교 횟수는 2N - 2s2(N) - e2(N) (이진 힙의 경우)와 같다.[7] 여기서 s2(N)는 N의 이진 표현의 모든 숫자의 합이고, e2(N)는 N의 소인수 분해에서 2의 지수이다. 이는 처음에 비어 있는 힙에 일련의 연속적인 삽입을 수행하는 것보다 빠르다.
다음은 배열을 사용하여 힙을 구현할 때의 장점이다.
- 임의의 요소에 대한 부모 요소와 자식 요소를 인덱스 계산으로 쉽게 찾을 수 있다.
- 전체 요소 수와 비교하여 요소의 존재 여부를 쉽게 판단할 수 있다.
- 포인터를 사용하지 않고도 데이터 자체를 저장한 배열만으로 힙 구조를 구현할 수 있다.
하지만, 개별 요소의 데이터 크기가 큰 경우에는 요소 교체 시 복사 처리의 부담이 커질 수 있다. 이 경우 각 요소에 대한 포인터(또는 각 요소의 인덱스)로 구성된 배열을 별도로 생성하여 힙 구조를 구현하는 방법을 고려할 수 있다.
다음의 배열을 이용하여 최대힙을 구성하는 예를 살펴 보자.
`[H|E|A|P|S|O|R|T]`
사전 순서에 따라 (즉, A < B < C < … < Y < Z) 힙으로 구성하는 과정은 다음과 같다.
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 설명 |
---|---|---|---|---|---|---|---|---|
H | E | A | P | S | O | R | T | 중간 지점인 p (4번째 인덱스)에서 시작 |
H | E | A | T | S | O | R | P | p의 왼쪽 자식(8번째 인덱스) T > P이므로 교환 |
H | E | R | T | S | O | A | P | A (3번째 인덱스)의 왼쪽 자식은 O (6번째 인덱스), 오른쪽 자식은 R (7번째 인덱스). R > O, R > A이므로 R과 A 교환 |
H | T | R | E | S | O | A | P | E (2번째 인덱스)의 왼쪽 자식은 T (4번째 인덱스), 오른쪽 자식은 S (5번째 인덱스). T > S, T > E이므로 T와 E 교환 |
H | T | R | P | S | O | A | E | E (4번째 인덱스)의 왼쪽 자식은 P (8번째 인덱스). P > E이므로 P와 E 교환 |
T | H | R | P | S | O | A | E | H (1번째 인덱스)의 왼쪽 자식은 T (2번째 인덱스), 오른쪽 자식은 R (3번째 인덱스). T > R, T > H이므로 T와 H 교환 |
T | S | R | P | H | O | A | E | H (2번째 인덱스)의 왼쪽 자식은 P (4번째 인덱스), 오른쪽 자식은 S (5번째 인덱스). S > P, S > H이므로 S와 H 교환. 힙 구성 완료. |
3. 3. 2. 삽입 연산
힙에 새로운 키를 추가하려면, 다음의 ''insert'' 연산 과정을 따른다.[4]# 힙의 끝, 즉 첫 번째 사용 가능한 빈 공간에 새 요소를 추가한다.
# 힙 속성이 위반되는 경우, 힙 속성이 다시 설정될 때까지 새 요소를 sift up (''swim'' 연산)한다.
예를 들어, 배열 [H|E|A|P|S|O|R|T]를 최대 힙으로 구성하는 과정은 다음과 같다. (배열은 사전 순서, 즉 A
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
H | E | A | P | S | O | R | T | |||||||
H | E | A | P | S | O | R | T | ^ | ^ | |||||
H | E | A | T | S | O | R | P | ^ | colspan="2" | | ^ | ^ | |||
H | E | R | T | S | O | A | P | ^ | ^ | ^ | ||||
H | T | R | E | S | O | A | P | ^ | ^ | ^ | ||||
H | T | R | P | S | O | A | E | ^ | ^ | ^ | ||||
T | H | R | P | S | O | A | E | ^ | ^ | ^ | ||||
T | S | R | P | H | O | A | E |
- 4번째 인덱스 P에서 시작한다. P의 왼쪽 자식 노드(8번째 인덱스) T와 비교하면 T>P이므로 둘을 교환한다.
- 3번째 인덱스 A로 이동한다. A의 왼쪽 자식 노드는 O(6번째 인덱스), 오른쪽 자식 노드는 R(7번째 인덱스)이다. R>O, R>A이므로 R과 A를 교환한다.
- 2번째 인덱스 E로 이동한다. E의 왼쪽 자식 노드 T(4번째 인덱스), 오른쪽 자식 노드 S(5번째 인덱스)와 비교한다. T>S, T>E이므로 T와 E를 교환한다.
- 계속해서 E(4번째 인덱스)는 왼쪽 자식 노드 P(8번째 인덱스)와 비교하는데, P>E이므로 P와 E를 교환한다.
- 1번째 인덱스 H로 이동한다. H의 왼쪽 자식 노드 T(2번째 인덱스), 오른쪽 자식 노드 R(3번째 인덱스)과 비교한다. T>R, T>H이므로 T와 H를 교환한다.
- 계속해서 H(2번째 인덱스)를 비교 교환한다. H의 왼쪽 자식 노드 P(4번째 인덱스), 오른쪽 자식 노드 S(5번째 인덱스)와 비교하면 S>P, S>H이므로 S와 H를 교환한다.
- 힙 구성이 완료된다.
힙 구성 결과, 루트 노드인 T는 그 자식 노드인 S, R과 비교할 때 T>S, T>R 관계를 가지며, 동일한 관계가 모든 노드와 그 자식 노드에 대해 성립한다.
이진 트리를 힙으로 구성하는 의사 코드는 다음과 같다.
makeTreeHeap(H,n) //H full binary Tree, data size n
for(i <= H/2;i>=1;i <- i-1) do //부모노드 레벨의 역행(4-,3-,2-,1번째)
{
p <- i;// 중간지점에서 시작
for(j<- 2*p;j<=n;j <- 2*j) do //해당 부모노드의 자식노드들에 대한 비교 교환
{
if(j
if(H[j] < H[j+1]) then j <- j+1
if(H[p] >= H[j]) exit;
temp <- H[p];
H[p] <- H[j];
H[j] <- temp;
p <- j;
}
}
힙은 일반적으로 배열로 구현되며, 각 요소는 힙의 노드를 나타낸다. 부모/자식 관계는 배열의 요소 인덱스를 통해 암시적으로 정의된다.
이진 힙의 경우, 배열에서 첫 번째 인덱스는 루트 요소를 포함한다. 배열의 다음 두 인덱스는 루트의 자식을, 다음 네 개의 인덱스는 루트의 두 자식 노드의 네 자식을 포함하는 식으로 구성된다. 따라서 인덱스 i의 노드가 주어지면, 자식은 인덱스 2i + 1과 2i + 2에 있으며, 부모는 인덱스 (i-1)/2 에 있다.
힙에 요소를 삽입한 후에는 힙 속성이 위반될 수 있으며, 배열 내에서 요소를 교환하여 힙의 균형을 다시 맞춰야 한다. 이때 ''sift-up'' 연산이 사용된다. ''sift-up''은 필요에 따라 노드를 트리 위로 이동하는 연산으로, 삽입 후 힙 조건을 복원하는 데 사용된다. 노드가 체와 같이 올바른 레벨에 도달할 때까지 트리 위로 이동하기 때문에 "sift"라고 불린다.
3. 3. 3. 삭제 연산
힙에서 루트 노드를 삭제하는 기본 절차는 다음과 같다.# 삭제 대상 요소를 (루트)로 설정하고, 힙의 마지막 요소 을 루트로 이동한다.
# 요소 을 자식 노드( 및 )와 비교한다. 자식 노드가 없거나(), 모든 자식 노드보다 요소 이 크거나 같으면 종료한다.
# 더 작은 값을 가진 자식 노드와 요소 을 교환하고, 을 교환된 자식 노드의 인덱스로 설정하여 반복한다.
루트 노드가 아닌 다른 노드를 삭제하는 경우에도 마지막 요소 을 삭제할 위치로 이동하는 것은 동일하다.
하지만 이후의 동작은 다음 조건 분기가 필요하다.
- 요소 N > 자식 요소인 경우: 루트 삭제와 마찬가지로, 리프 노드를 향해 교환 작업을 반복한다.
- 요소 N < 부모 요소인 경우: 삽입 연산과 같이, 루트 노드를 향해 교환 작업을 반복한다.
삭제 전에 올바른 힙 구조가 유지되었다면, 부모 요소 ≤ 자식 요소이므로, 두 조건이 동시에 성립하는 경우는 없다.
3. 3. 4. 기타 연산
;기본 연산- '''find-max'''(또는 '''find-min'''): 최대 힙의 최대 항목 또는 최소 힙의 최소 항목을 각각 찾는다. (별칭: '''peek''')
- '''insert''': 힙에 새 키를 추가한다. (별칭: ''push''[4])
- '''extract-max'''(또는 '''extract-min'''): 최대 힙에서 최대 값을 가진 노드를 반환하고 힙에서 제거한다. (또는 최소 힙에서 최소 값을 반환한다.) (별칭: ''pop''[5])
- '''delete-max'''(또는 '''delete-min'''): 최대 힙(또는 최소 힙)의 루트 노드를 각각 제거한다.
- '''replace''': 루트를 pop하고 새 키를 push한다. 이 연산은 pop한 다음 push하는 것보다 효율적인데, 균형을 한 번만 맞추면 되기 때문이다. 고정 크기 힙에 적합하다.[6]
;생성 연산
- '''create-heap''': 빈 힙을 생성한다.
- '''heapify''': 주어진 요소의 배열로 힙을 생성한다.
- '''merge'''(''union''): 두 힙을 결합하여 두 힙의 모든 요소를 포함하는 유효한 새 힙을 형성하고, 원래 힙은 보존한다.
- '''meld''': 두 힙을 결합하여 두 힙의 모든 요소를 포함하는 유효한 새 힙을 형성하고, 원래 힙은 파괴한다.
;검사 연산
- '''size''': 힙의 항목 수를 반환한다.
- '''is-empty''': 힙이 비어 있으면 true를 반환하고, 그렇지 않으면 false를 반환한다.
;내부 연산
- '''increase-key''' 또는 '''decrease-key''': 각각 최대 힙 또는 최소 힙 내의 키를 업데이트한다.
- '''delete''': 임의의 노드를 삭제한다. (마지막 노드를 이동하고 힙을 유지하기 위해 시프트 수행).
- '''sift-up''': 필요에 따라 노드를 트리 위로 이동한다. 삽입 후 힙 조건을 복원하는 데 사용된다. 노드가 체와 같이 올바른 레벨에 도달할 때까지 트리 위로 이동하기 때문에 "sift"라고 한다.
- '''sift-down''': sift-up과 유사하게 노드를 트리 아래로 이동한다. 삭제 또는 교체 후 힙 조건을 복원하는 데 사용된다.
4. 응용
- 힙 정렬: 제자리 정렬 방식이며 최악의 경우에도 이차 시간이 걸리지 않는 최고의 정렬 방법 중 하나이다.
- 선택 알고리즘: 힙은 최소 또는 최대 요소에 상수 시간에 접근할 수 있으며, 중앙값 또는 k번째 요소와 같은 다른 선택은 힙에 있는 데이터에서 서브 선형 시간 내에 수행할 수 있다.[8]
- 그래프 알고리즘: 힙을 내부 순회 데이터 구조로 사용하면 실행 시간이 다항식 차수만큼 줄어든다. 이러한 문제의 예로는 프림의 최소 신장 트리 알고리즘과 다익스트라의 최단 경로 알고리즘이 있다.
- 우선순위 큐: 우선순위 큐는 "목록" 또는 "맵"과 같은 추상적인 개념이며, 목록을 연결 목록 또는 배열로 구현할 수 있는 것처럼 우선순위 큐는 힙 또는 다양한 다른 방법으로 구현할 수 있다.
- K-웨이 병합: 힙 자료 구조는 이미 정렬된 여러 입력 스트림을 단일 정렬된 출력 스트림으로 병합하는 데 유용하다. 병합의 필요성의 예로는 외부 정렬 및 로그 구조 병합 트리와 같은 분산 데이터에서 스트리밍 결과가 있다. 내부 루프는 최소 요소를 얻고, 해당 입력 스트림에 대한 다음 요소로 대체한 다음, 힙 다운 연산을 수행한다. (또는 대체 기능). (우선순위 큐의 최대 추출 및 삽입 함수를 사용하는 것은 훨씬 덜 효율적이다.)
피보나치 힙의 경우, 삽입, 최소값 검색, 병합이 일정 상각 시간 내에 수행되며, 삭제는 ''O''(log ''n'')으로 수행할 수 있다.
5. 프로그래밍 언어별 구현
- C언어로 구현된 최대 힙의 예시는 다음과 같다.
void swap(int *a,int *b)
{
int tmp;
tmp=*a;
- a=*b;
- b=tmp;
}
int max(int a,int b,int c)
{
int max=0,q;
if(tree[a]>max) {
max=tree[a];
q=a;
}
if(tree[b]>max) {
max=tree[b];
q=b;
}
if(tree[c]>max) {
max=tree[c];
q=c;
}
return q;
}
void create_heap()
{
end=N/2;
for(i=end;i>=1;i--) {
nnow=i;
while((qq=max(nnow,nnow*2,nnow*2+1))!=nnow) {
swap(&tree[nnow],&tree[qq]);
nnow=qq;
}
}
}
- C++ 표준 라이브러리는 힙에 대한 , 및 알고리즘을 제공한다.
- 부스트 C++ 라이브러리에는 ''d''-ary, 이항, 피보나치, 페어링 및 스큐 힙과 같은 추가 유형의 힙을 지원하는 힙 라이브러리가 포함되어 있다.
- C 및 C++를 위한 [https://github.com/valyala/gheap 일반 힙 구현]이 있으며, D-ary 힙 및 B-힙을 지원한다.
- D 프로그래밍 언어의 표준 라이브러리에는 [https://dlang.org/phobos/std_container_binaryheap.html ]가 포함되어 있다.
- Haskell의 경우 [https://hackage.haskell.org/package/heaps ] 모듈이 있다.
- Java 플랫폼은 Java 컬렉션 프레임워크의 클래스를 사용하여 이진 힙 구현을 제공한다.
- Python에는 이진 힙을 사용하여 우선 순위 큐를 구현하는 [https://docs.python.org/library/heapq.html ] 모듈이 있다.
- PHP는 표준 PHP 라이브러리의 버전 5.3부터 최대 힙 ()과 최소 힙 ()을 모두 가지고 있다.
- Perl은 CPAN에서 사용할 수 있는 [https://metacpan.org/module/Heap ] 배포판에 이진, 이항 및 피보나치 힙의 구현을 가지고 있다.
- Go 언어에는 [http://golang.org/pkg/container/heap/ ] 패키지가 포함되어 있다.
- 애플의 Core Foundation 라이브러리에는 [https://developer.apple.com/documentation/corefoundation/cfbinaryheap ] 구조체가 포함되어 있다.
- Pharo는 Collections-Sequenceable 패키지에 힙 구현을 가지고 있다.
- Rust 프로그래밍 언어는 표준 라이브러리의 모듈에 이진 최대 힙 구현인 [https://doc.rust-lang.org/std/collections/struct.BinaryHeap.html ]를 가지고 있다.
- .NET은 [https://docs.microsoft.com/dotnet/api/system.collections.generic.priorityqueue-2 PriorityQueue] 클래스를 가지고 있다.
참조
[1]
간행물
"heap" in [[Dictionary of Algorithms and Data Structures]]
https://xlinux.nist.[...]
National Institute of Standards and Technology
2004-12-14
[2]
서적
INTRODUCTION TO ALGORITHMS
The MIT Press Cambridge, Massachusetts London, England
[3]
논문
Algorithm 232 - Heapsort
[4]
웹사이트
heapq.heappush
https://docs.python.[...]
[5]
웹사이트
heapq.heappop
https://docs.python.[...]
[6]
웹사이트
heapq.heapreplace
https://docs.python.[...]
[7]
논문
Elementary Yet Precise Worst-Case Analysis of Floyd's Heap-Construction Program
IOS Press
[8]
논문
Information and Computation
http://ftp.cs.purdue[...]
Academic Press
2010-10-31
[9]
문서
레벨 포화 상태(saturated status on level)
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com